#!/usr/local/bin/python3
# *_* coding: UTF-8 *_*
# @IDE: PyCharm
# @Version: Python3.97
# @Author: Kendrick.Kc
# @Email: 509556985@qq.com
# @File: middle.py
# @Inst: 中间件
# @Time: 2022/8/17 22:05
# -----


import time
import traceback


class Middle:

    def __init__(self, instId, GRID, m_lock, t_lock, binance):
        self.symbol = instId
        self.binance = binance
        self.dgs_m_lock = m_lock  # 进行锁
        self.dgs_t_lock = t_lock  # 线程锁
        self._ = {
            "BOO": True,     # 默认为持仓
            "LONG_BOO": True,
            "SHORT_BOO": True,
            "DGS_BOO": True,
        }  # 临时参数
        self.is_exit = True  # 当前交易对是否继续交易
        self.last_region = []    # 上一次的区域
        self.new_region = []    # 当前区域
        self.res_db = GRID
        self.trading_start()

    def trading_start(self):
        """ 交易开始的操作 """
        self.binance.logger_info(f"{self.symbol} 交易对启动......")
        self.Initialize()
        self.outside()      # 启动时如果价格在dgs外，则结束该交易对
        while True:
            time.sleep(0.5)
            try:
                if not self.is_exit:
                    break
                self.dgs_run()
            except Exception:
                self.binance.logger_error(f"死循环ERROR: {traceback.format_exc()}")
        # raise ValueError(f"异常处理停止该{self.symbol}交易对线程.")

    def trading_end(self):
        """ 交易结束的操作 """

        def delete_trading_symbol(log_trading=0):
            """ 交易结束 将trading中交易对的金额添加进balance并从trading中移除 """

            """ 多进程多线程间，相同数据并行问题(模拟队列)处理 ### 目前使用随机时间顶替 ### """
            self.dgs_t_lock.acquire()
            self.dgs_m_lock.acquire()
            # ---
            balance = self.binance.cache_get(self.binance.parameter.db_coll_balance)
            trading = self.binance.cache_get(self.binance.parameter.db_coll_trading_symbol)
            for i in range(len(trading)):
                if trading[i]["symbol"] == self.symbol:
                    log_trading = trading[i]
                    balance[self.binance.parameter.db_coll_balance] = balance[self.binance.parameter.db_coll_balance] + trading[i]["real_u"]
                    trading.remove(trading[i])
                    break
                    
            self.binance.cache_set(self.binance.parameter.db_coll_trading_symbol, trading)
            self.binance.cache_set(self.binance.parameter.db_coll_balance, balance)
                
            # ---
            self.binance.logger_info(f"【{self.binance.pan_openings['note']}】{self.symbol} 交易对结束."
                                     f"启动金额: {round(self.res_db['info']['real_u'], 2)}U, "
                                     f"当前结束余额: {round(log_trading['real_u'], 2)}U")
            self.dgs_m_lock.release()
            self.dgs_t_lock.release()

        delete_trading_symbol()
        self.is_exit = False

    def Initialize(self):
        """ 初始化: 设置杠杆与逐全仓模式 """
        if not self.binance.pan_openings["pan"]:  # 实盘时才设置
            self.binance.rest('info', 'change_leverage', symbol=self.symbol, leverage=self.res_db["info"]["leverage"])
            self.binance.rest('info', 'change_margin_type', symbol=self.symbol, marginType=self.res_db["info"]["isolated"])
        return

    def get_pos(self):
        """ 获取持仓信息 """
        if self.binance.pan_openings["pan"]:
            # 模拟交易状态
            res = False
            res_db = self.res_db["db"]
            db = [res_db["buy_kai_long"], res_db["sell_kai_short"], res_db["speed_tracking"]]
            for item in db:
                if res:
                    break
                for _ in item:
                    if _["state"] == "pending":
                        res = True
                        self._["BOO"] = True
                        break
                    self._["BOO"] = False

        else:
            # 真实交易状态
            position = self.binance.rest("while_info", "get_position_risk", symbol=self.symbol)  # get_position_risk
            for pos in position:  # 遍历过滤交易货币
                if pos['positionSide'] == "LONG":
                    self._['get_pos_long'] = pos
                else:
                    self._['get_pos_short'] = pos

            if float(self._["get_pos_long"]["positionAmt"]) == 0:
                self._["BOO"] = False
            else:
                self._["BOO"] = True

            if not self._["BOO"] and float(self._["get_pos_short"]["positionAmt"]) == 0:
                self._["BOO"] = False
            else:
                self._["BOO"] = True

            # 细分判断持仓
            self._["LONG_BOO"] = False if float(self._["get_pos_long"]["positionAmt"]) == 0 else True
            self._["SHORT_BOO"] = False if float(self._["get_pos_short"]["positionAmt"]) == 0 else True

    def get_dgs_pos(self):
        """
        判断DGS是否存在持仓
        """
        res = False
        res_db = self.res_db["db"]
        db = [res_db["buy_kai_long"], res_db["sell_kai_short"]]
        for item in db:
            if res:
                break
            for _ in item:
                if _["state"] == "pending":
                    res = True
                    self._["DGS_BOO"] = True
                    break
                self._["DGS_BOO"] = False

    def open_pos_real_u(self, amount, price):
        """
        开仓下单后更新trading数据表real_u数据
        :param amount: 下单数量
        :param price: 下单价格
        :return:
        """
        self.dgs_t_lock.acquire()
        self.dgs_m_lock.acquire()

        real_u = (amount * price) / self.res_db["info"]["leverage"]  # 真实使用的U
        real_u = real_u - (real_u * self.binance.parameter.commission)  # 计算手续费

        res_trading = self.binance.cache_get(self.binance.parameter.db_coll_trading_symbol)
        for i in range(len(res_trading)):
            if res_trading[i]["symbol"] == self.symbol:
                res_trading[i]["real_u"] = res_trading[i]["real_u"] - real_u
                self.binance.cache_set(self.binance.parameter.db_coll_trading_symbol, res_trading)

        self.dgs_m_lock.release()
        self.dgs_t_lock.release()

    def flat_pos_real_u(self, pos, amount, open_price, flat_price):
        """
        平仓下单后更新trading数据表real_u数据
        :param pos: 仓位方向
        :param amount: 下单数量
        :param open_price: 开仓价格
        :param flat_price: 下单价格
        :return:
        """
        self.dgs_t_lock.acquire()
        self.dgs_m_lock.acquire()

        if pos == "LONG":
            real_u = (amount * flat_price) / self.res_db["info"]["leverage"]  # 真实使用的U
        else:
            real_price = open_price + (open_price - flat_price)  # 做空真实价格
            real_u = (amount * real_price) / self.res_db["info"]["leverage"]  # 真实使用的U
        real_u = real_u - (real_u * self.binance.parameter.commission)

        res_trading = self.binance.cache_get(self.binance.parameter.db_coll_trading_symbol)
        for i in range(len(res_trading)):
            if res_trading[i]["symbol"] == self.symbol:
                res_trading[i]["real_u"] = res_trading[i]["real_u"] + real_u
                self.binance.cache_set(self.binance.parameter.db_coll_trading_symbol, res_trading)

        self.dgs_m_lock.release()
        self.dgs_t_lock.release()

    # 当前区域 区域变更时则为开/平仓信号
    def cut_region(self):
        def region():
            ex_price = self.get_new_ticker_price(self.symbol)
            dgs = self.res_db["db"]["buy_kai_long"]
            for i in range(len(dgs)):
                if i == 0 and dgs[i]['price'] <= ex_price <= dgs[i]['beginPrice']:
                    return [101, i]   # 与天价相邻的区域
                if i == len(dgs) - 1 and dgs[i]['price'] >= ex_price >= dgs[i]['endPrice']:
                    return [99, i]  # 与地价相邻的区域
                if i != len(dgs) - 1 and dgs[i]['price'] >= ex_price >= dgs[i + 1]['price']:
                    return [i, i + 1]   # 其它区域
            return []

        if not self.last_region:
            self.last_region = region()
        self.new_region = region()
        return self.new_region

    # 如果价格在dgs外，则结束该交易对
    def outside(self):

        if not self.last_region:
            ex_price = self.get_new_ticker_price(self.symbol)
            if ex_price > self.res_db["db"]["speed_tracking"][0]["buyPrice"] \
                    or ex_price < self.res_db["db"]["speed_tracking"][1]["buyPrice"]:
                self.get_pos()
                if not self._["BOO"]:
                    self.binance.logger_info(f"{self.symbol}交易对启动时价格大于天/地价，停止并更换交易对.")
                    self.dgs_t_lock.acquire()
                    self.dgs_m_lock.acquire()

                    volatility = self.binance.cache_get(self.binance.parameter.db_coll_high_volatility)
                    vol_symbol = volatility["vol"]
                    for i in range(len(vol_symbol)):
                        if vol_symbol[i]["symbol"] == self.symbol:
                            vol_symbol.remove(vol_symbol[i])
                            volatility["vol"] = vol_symbol
                            self.binance.cache_set(self.binance.parameter.db_coll_high_volatility, volatility)
                            break

                    self.dgs_m_lock.release()
                    self.dgs_t_lock.release()
                    self.trading_end()

    def stop_orders(self, _stop_orders_long_number, _stop_orders_short_number, messages):
        """
        平仓
        :param _stop_orders_long_number: long仓数量
        :param _stop_orders_short_number: short仓数量
        :param messages: 日志消息
        :return:
        """

        def _post_order(num, side, pos_side):
            """ 对接交易所平仓 """
            self.binance.rest("info", "new_order_test" if self.binance.pan_openings["pan"] else "new_order",
                              symbol=self.symbol, side=side, positionSide=pos_side, type="MARKET", quantity=abs(num))

        if float(_stop_orders_long_number) > 0:
            _post_order(_stop_orders_long_number, "SELL", "LONG")
        if float(_stop_orders_short_number) > 0:
            _post_order(_stop_orders_short_number, "BUY", "SHORT")
        self.binance.logger_info(f"{messages}")

    def buy_average_price(self):
        """ 计算买入均价 """
        long, long_number, short, short_number = 0, 0, 0, 0
        for i in range(len(self.res_db["db"]["buy_kai_long"])):
            if self.res_db["db"]["buy_kai_long"][i]["state"] == "pending":
                long = long + self.res_db["db"]["buy_kai_long"][i]["price"]
                long_number += 1

            if self.res_db["db"]["sell_kai_short"][i]["state"] == "pending":
                short = short + self.res_db["db"]["sell_kai_short"][i]["price"]
                short_number += 1

        long_average_price = long / long_number
        short_average_price = short / short_number
        return long_average_price, short_average_price

    def res_dgs_db_orders(self):
        """ 计算dgs数量 """
        rdo_long_number, rdo_short_number = 0, 0
        for i in range(len(self.res_db["db"]["buy_kai_long"])):
            if self.res_db["db"]["buy_kai_long"][i]["state"] == "pending":
                rdo_long_number = rdo_long_number + self.res_db["db"]["buy_kai_long"][i]["amount"]
                self.res_db["db"]["buy_kai_long"][i]["state"] = "idle"
                self.flat_pos_real_u("LONG", self.res_db["db"]["buy_kai_long"][i]["amount"],
                                     self.res_db["db"]["buy_kai_long"][i]["price"],
                                     self.res_db["db"]["buy_kai_long"][i]["coverPrice"])

            if self.res_db["db"]["sell_kai_short"][i]["state"] == "pending":
                rdo_short_number = rdo_short_number + self.res_db['db']['sell_kai_short'][i]['amount']
                self.res_db["db"]["sell_kai_short"][i]["state"] = "idle"
                self.flat_pos_real_u("SHORT", self.res_db["db"]["sell_kai_short"][i]["amount"],
                                     self.res_db["db"]["sell_kai_short"][i]["price"],
                                     self.res_db["db"]["sell_kai_short"][i]["coverPrice"])

        return rdo_long_number, rdo_short_number

    def res_speed_tracking_db_orders(self, st_long_number=0, st_short_number=0):
        """ 计算急速仓位数量 """
        if self.res_db["db"]["speed_tracking"][0]["state"] == "pending":
            st_long_number = st_long_number + self.res_db["db"]["speed_tracking"][0]["amount"]
            self.res_db["db"]["speed_tracking"][0]["state"] = "idle"

        if self.res_db["db"]["speed_tracking"][1]["state"] == "pending":
            st_short_number = st_short_number + self.res_db["db"]["speed_tracking"][1]["amount"]
            self.res_db["db"]["speed_tracking"][1]["state"] = "idle"

        if not self.binance.pan_openings["pan"]:
            for item in self.binance.position_risk(self.symbol):
                if item["positionSide"] == "LONG":
                    st_long_number = float(item["positionAmt"])
                elif item["positionSide"] == "SHORT":
                    st_short_number = float(abs(float(item["positionAmt"])))

        return st_long_number, st_short_number

    def get_new_ticker_price(self, symbol):
        while True:
            time.sleep(0.5)
            ticker_list = self.binance.cache_get(self.binance.parameter.db_coll_symbol_new_ticker_price)
            if ticker_list:
                for item in ticker_list:
                    if item["symbol"] == symbol:
                        return float(item["price"])